package com.atguigu.tingshu.common.aspect;

import com.atguigu.tingshu.common.annotation.CheckLogin;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * @author 路飞
 * @date 2024/7/27 20:32
 * @description 这个作为一个切面类,来具体实现@CheckLogin注解的作用
 */
@Slf4j
@Aspect
@Component
public class CheckLoginAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * @description 使用一个环绕通知来实现校验登录功能
     * 1.首先需要从请求中获取token,通过token从redis中获取数据,判断用户是否存在
     * 2.如果不存在,并且针对需要拦截的方法,那么返回一个错误码,让前端进行重定向到登录页面
     * 3.如果存在,将用户信息保存到ThreadLocal中
     */
    @Around("execution(* com.atguigu.tingshu.*..api.*.*(..)) && @annotation(checkLogin)")
    public Object checkLogin(ProceedingJoinPoint joinPoint, CheckLogin checkLogin) {
        log.info("checkLogin start...");
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String token = request.getHeader("token");
        UserInfoVo userInfoVo = (UserInfoVo) redisTemplate.opsForValue().get(RedisConstant.USER_LOGIN_KEY_PREFIX + token);
        if (checkLogin.required() && Objects.isNull(userInfoVo)) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }

        if (Objects.nonNull(userInfoVo)) {
            AuthContextHolder.setUserId(userInfoVo.getId());
        }
        Object result;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        // 当这次请求结束之后,就清空ThreadLocal中的数据,防止读取到脏数据
        AuthContextHolder.removeUserId();
        return result;
    }
}
